package sg.edu.nus.iss.vmcs.customer;

import java.util.Arrays;

import sg.edu.nus.iss.vmcs.store.CashStore;
import sg.edu.nus.iss.vmcs.store.Coin;
import sg.edu.nus.iss.vmcs.store.Store;
import sg.edu.nus.iss.vmcs.store.StoreItem;
import sg.edu.nus.iss.vmcs.util.VMCSException;

/*
 * Copyright 2003 ISS.
 * The contents contained in this document may not be reproduced in any
 * form or by any means, without the written permission of ISS, other
 * than for the purpose for which it has been supplied.
 *
 */

/**
 * This control object manages the giving of change to the Customer
 */
public class ChangeGiver {
	
	private static final String LABEL_NO_CHANGE = "0 C";

	/**
	 * Transaction controller controlling this controller
	 */
	private TransactionController transactionController;
	
	/**
	 * Default constructor
	 * 
	 * @param transactionController
	 *            Transaction controller controlling this controller
	 */
	public ChangeGiver(TransactionController transactionController) {
		this.transactionController = transactionController;
	}
	
	/**
	 * This operation is used to display the appropriate message on the No
	 * Change Available Display depending on the current change availability.
	 */
	public void displayChangeStatus() {
		
		boolean changeAvailable = false;
		
		// get the cash store
		CashStore cashStore = (CashStore) this.transactionController
				.getMainController().getStoreController().getStore(Store.CASH);
		
		// determine the change status
		for (int i = 0; i < cashStore.getStoreSize(); i++) {
			StoreItem currItem = cashStore.getStoreItem(i);
			if (currItem.getQuantity() > 0) {
				changeAvailable = true;
				break;
			}
		}
		
		// update the display
		this.transactionController.getCustomerPanel()
				.getNoChangeAvailableDisplay().setState(!changeAvailable);
		
	}
	
	/**
	 * This operation manages the issuing of change to the Customer
	 * 
	 * @param changeRequired
	 *            The amount of change required
	 * @return TRUE if change is available
	 */
	public boolean giveChange(int changeRequired) {
		
		// get the cash store
		CashStore cashStore = (CashStore) this.transactionController
				.getMainController().getStoreController().getStore(Store.CASH);

		// initialize value and quantity arrays
		int storeSize = cashStore.getStoreSize();
		int[] value = new int[storeSize];
		int[] quantity = new int[storeSize];
		for (int i = 0; i < storeSize; i++) {
			value[i] = ((Coin) cashStore.getStoreItem(i).getContent())
					.getValue();
			quantity[i] = cashStore.getStoreItem(i).getQuantity();
		}

		// calculate the change
		int[] changeQuantity = calculateChange(changeRequired, value, quantity);

		// give out the change
		for (int i = 0; i < changeQuantity.length; i++) {
			if (changeQuantity[i] > 0) {
				try {
//					if(i == 2)
//					{
//						throw new VMCSException();
//					}
					this.transactionController.getMainController()
							.getMachineryController().giveChange(i,
									changeQuantity[i]);
				} catch (VMCSException e) {
					this.transactionController
							.terminateFault(TransactionController.FAULT_TYPE_CHANGE);
					return false;
				}
			}
		}

		// update the display
		int changeAmount = computeTotalValue(value, changeQuantity);
		this.transactionController.getCustomerPanel()
				.getRefundChangeTrayDisplay().setValue(changeAmount + " C");
		this.transactionController.getCustomerPanel()
				.getNoChangeAvailableDisplay().setState(
						changeRequired != changeAmount);

		return true;

	}
	
	/**
	 * This operation is used to reset the Refund/Change Tray display on the 
	 * Customer Panel
	 */
	public void resetChange() {
		this.transactionController.getCustomerPanel()
				.getRefundChangeTrayDisplay().setValue(LABEL_NO_CHANGE);
	}
	
	/**
	 * Calculate change using a recursive exhaustive search to determine the
	 * most optimal change. Most optimal change here means that the exact change
	 * will be given or an amount that is closest to the required change amount
	 * if the required change cannot be returned
	 * 
	 * @param changeRequired
	 *            Change required
	 * @param value
	 *            Array containing the values
	 * @param quantity
	 *            Array containing the quantities
	 * @return Array containing the change quantity
	 */
	private int[] calculateChange(int changeRequired, int[] value,
			int[] quantity) {

		int[] result = new int[quantity.length];
		Arrays.fill(result, 0);
		if (changeRequired == 0) {
			return result;
		} else {
			int[] best = result;
			for (int i = 0; i < value.length; i++) {
				if (quantity[i] > 0 && value[i] <= changeRequired) {
					quantity[i]--;
					int[] currResult = calculateChange(changeRequired
							- value[i], value, quantity);
					currResult[i]++;
					if (best == null
							|| computeTotalValue(value, best) < computeTotalValue(
									value, currResult)) {
						best = currResult;
					}
					quantity[i]++;
				}
			}
			return best;
		}
	}
	
	/**
	 * Compute the total value
	 * 
	 * @param value
	 *            An array holdig the values
	 * @param quantity
	 *            An array holding the quantities
	 * @return The total value
	 */
	private int computeTotalValue(int[] value, int[] quantity) {
		int result = 0;
		for (int i = 0; i < value.length; i++) {
			result = result + value[i] * quantity[i];
		}
		return result;
	}
	
}
